/*
 * Copyright © 2009 Gerald Isaac Schwarz
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package cli;

import java.util.*;
import java.awt.Point;
import java.awt.Color;
import java.io.File;
import driver.*;
import driver.Set;

public class CLI {
	public static final Character NEWLINE = Character.LINE_SEPARATOR;
	
	private ArrayList<Set> sets;
	private Scanner sc = new Scanner(System.in);
	private Driver driver;
	private ArrayList<Character> chars = new ArrayList<Character>();
	private ArrayList<String> exitValues = new ArrayList<String>();
	private Point movedPieceDestination;
	private TurnListener turnListener = new TurnListener() {
		public void turnPerformed(TurnEvent te) {
			if (!driver.isInCheckmate(driver.getCurrentPlayerIndex())) {
				printBoard();
				showMenu();
			}
			else {
				printBoard();
				System.out.println("Checkmate on player " + driver.getCurrentPlayer().getName() + "!");
			}
		}
	};
	
	private boolean isExitValue(String value) {
		for (String exitValue : exitValues) {
			if (value.equals(exitValue)) {
				return true;
			}
		}
		
		return false;
	}
	
	private int getInt() {
		String input = sc.nextLine();
		
		if (this.isExitValue(input)) {
			this.exit();
		}
		else if(this.isSaveValue(input)) {
			this.save();
		}
		else if(this.isLoadValue(input)) {
			this.load();
		}
		
		try {
			return Integer.parseInt(input);
		}
		catch (java.lang.NumberFormatException e) {
			System.out.println("Invalid input. Please try again:");
			return this.getInt();
		}
	}
	
	private String getString() {
		String input = sc.nextLine();
		
		if (this.isExitValue(input)) {
			this.exit();
		}
		else if(this.isSaveValue(input)) {
			this.save();
		}
		else if(this.isLoadValue(input)) {
			this.load();
		}
		
		return input;
	}
	
	private boolean isLoadValue(String value) {
		if (value.equals("load")) {
			return true;
		}
		else {
			return false;
		}
	}

	private boolean isSaveValue(String value) {
		if (value.equals("save")) {
			return true;
		}
		else {
			return false;
		}
	}

	private void load() {
		System.out.println("What file do you wish to load?");
		String input = sc.nextLine();
		System.out.println();
		
		File file = new File(input);
		
		if (file.exists()) {
			this.driver = (new GameLoader(file)).getDriver();
			
			this.driver.addTurnListener(this.turnListener);
			
			for (String type : this.driver.getTypes()) {
				while (this.alreadyExists(this.getCharRepresentation(type))) {
					char ch = this.getCharRepresentation(type);
					char[] t = type.toCharArray();
					type = "";
					for (char c : t) {
						if (c != ch) {
							type += c;
						}
					}
				}
				
				this.chars.add(type.charAt(0));
			}
			
			this.printBoard();
			this.showMenu();
		}
		else {
			System.out.println("Specified file doesn't exist.");
			this.load();
		}
	}
	
	private void save() {
		System.out.println("Where do you want to save the game?");
		String output = sc.nextLine();
		System.out.println();
		
		File file = new File(output);
		
		if (!file.exists()) {
			new GameSaver(this.driver, file);
		}
		else {
			System.out.println("Specified file already exists.");
			this.save();
		}
	}
	
	private void exit() {
		System.out.println("Are you sure you wish to exit the game?");
		System.out.println("[Y] Yes");
		System.out.println("[N] No");
		
		String input = sc.nextLine();
		System.out.println();
		
		if (input.equalsIgnoreCase("Y")) {
			System.exit(0);
		}
		else if (!input.equalsIgnoreCase("N")) {
			System.out.println("Invalid input");
			this.exit();
		}
	}
	
	private void printBoard() {
		for (int y = 0; y < driver.getBoardSize().height; y++) {
			for (int x = 0; x < driver.getBoardSize().width; x++) {
				Point location = new Point(x, y);
				String label;
				
				if (!driver.isEmpty(location)) {
					Piece piece = driver.getPiece(location);
					
					label = this.getRepresentation(piece);
					
					if (piece.getOwner().getColor().equals(Color.BLACK)) {
						label = label.toUpperCase();
					}
					else {
						label = label.toLowerCase();
					}
				}
				else {
					label = "*";
				}
				
				System.out.print(label);
			}
			
			System.out.println();
		}
	}
	
	
	private void drop() {
		ArrayList<Piece> pieces = driver.getCapturedPieces(driver.getCurrentPlayerIndex());
		Point destination = new Point(-1, -1);
		
		System.out.println("Please Select a piece:");
		
		for (int i = 0; i < pieces.size(); i++) {
			System.out.println("[" + i + "] " + pieces.get(i).getType() + CLI.NEWLINE);
		}
		
		System.out.print("? ");
		int index = this.getInt();
		
		System.out.println("Please Select a destination:");
		
		System.out.print("X: ");
		destination.x = this.getInt();
		
		
		System.out.print("Y: ");
		destination.y = this.getInt();
		
		try {
			driver.drop(index, destination);
		}
		catch (DropNotSupportedException e) {
			System.out.println("Dropping isn't legal in this game.");
			this.showMenu();
		}
		catch (InvalidDropException e) {
			System.out.println("Invalid input.");
			this.showMenu();
		}
	}
	
	
	private void move() {
		Player player = driver.getCurrentPlayer();
		Piece piece;
		Point origin = new Point();
		Point destination = new Point(-1, -1);
		
		System.out.println(player.getName() + ", please select a piece:");
		
		System.out.print("X: ");
		origin.x = this.getInt();
		
		System.out.print("Y: ");
		origin.y = this.getInt();
		
		piece = driver.getPiece(origin);
		
		if (!driver.isEmpty(origin) && piece.getOwner() == player) {
			System.out.println("Please select a destination:");
			
			System.out.print("X: ");
			destination.x = this.getInt();
			
			System.out.print("Y: ");
			destination.y = this.getInt();
			
			try {
				driver.move(piece.getLocation(), destination);
				this.movedPieceDestination = destination;
			}
			catch (InvalidMoveException e) {
				System.out.println("Invalid input.");
				this.showMenu();
			}
			catch (MoveNotSupportedException e) {
				System.out.println("Moving isn't legal in this game.");
				this.showMenu();
			}
		}
		else {
			System.out.println("Invalid input.");
			this.showMenu();
		}
	}
	
	
	private void upgrade() {
		Piece piece = this.driver.getPiece(this.movedPieceDestination);
		boolean allowCancel = this.driver.mustBeUpgraded(piece.getLocation()) ? false : true;
		
		System.out.println("What upgrade would you like to apply to your " + piece.getType() + (allowCancel ? "? (use -1 to cancel)" : "?"));
			
		for (int i = 0; i < driver.getUpgrades(piece.getLocation()).size(); i++) {
			System.out.println("[" + i + "] " + driver.getUpgrades(piece.getLocation()).get(i));
		}
		System.out.print("? ");
		int action = this.getInt();
		
		if (action >= 0 && action < driver.getUpgrades(piece.getLocation()).size()) {
			String upgrade = driver.getUpgrades(piece.getLocation()).get(action);
			try {
				driver.upgrade(piece.getLocation(), upgrade);
				System.out.println("Your " + piece.getType() + " has been promoted to a " + upgrade);
			}
			catch (InvalidUpgradeException e) {
				System.out.println("Error. Please try again.");
				upgrade();
			}
			catch (UpgradeNotSupportedException e) {
				System.out.println("Upgrading isn't legal in this game.");
				return;
			}
		}
		else if (action == -1 && allowCancel){
			return;
		}
		else {
			System.out.println("Invalid input. Please try again.");
			upgrade();
		}
	}
	
	private void initializePlayers() {
		ArrayList<Player> players = new ArrayList<Player>();
		System.out.println("Player 1, please enter your name: ");
		players.add(new Player(this.getString(), Player.HUMAN, Color.WHITE));
		
		System.out.println("Player 2, please enter your name: ");
		players.add(new Player(this.getString(), Player.HUMAN, Color.BLACK));
		
		try {
			driver.setPlayers(players);
		}
		catch (Exception e) {
			System.out.println("Error. Please try again.");
			e.printStackTrace();
			this.initializePlayers();
		}
	}
	
	private void performMove() throws TurnNotFinishedException {
		this.move();
		
		if (this.movedPieceDestination != null && this.driver.canBeUpgraded(this.movedPieceDestination)) {
			this.upgrade();
		}
		
		this.driver.endTurn();
	}
	
	private void performDrop() throws TurnNotFinishedException {
		this.drop();
		this.driver.endTurn();
	}
	
	private void showMenu() {
		try {
			if (this.driver.getCapturedPieces(this.driver.getCurrentPlayerIndex()).size() > 0) {
				System.out.println(this.driver.getCurrentPlayer().getName() + ", What do you wish to do?");
				System.out.println("[1] Move");
				System.out.println("[2] Drop");
				System.out.print("? ");
				int action = this.getInt();
				
				if (action == 1) {
					this.performMove();
				}
				else if (action == 2) {
					this.performDrop();
				}
				else {
					this.invalid("Invalid selection");
					this.showMenu();
				}
			}
			else {
				this.performMove();
			}
		}
		catch (TurnNotFinishedException e) {
			this.upgrade();
		}
	}
	
	private CLI() {
		String set = "shogi";
		ArrayList<String> pieces = new ArrayList<String>();
		pieces.add("King");
		pieces.add("Rook");
		pieces.add("Bishop");
		pieces.add("GoldGeneral");
		pieces.add("SilverGeneral");
		pieces.add("Knight");
		pieces.add("Lance");
		pieces.add("Pawn");
		pieces.add("UpgradedRook");
		pieces.add("UpgradedBishop");
		
		ArrayList<String> representations = new ArrayList<String>();
		representations.add("K");
		representations.add("R");
		representations.add("B");
		representations.add("G");
		representations.add("S");
		representations.add("N");
		representations.add("L");
		representations.add("P");
		representations.add("O");
		representations.add("I");
		
		this.sets = new ArrayList<Set>();
		this.sets.add(new Set(set, pieces, representations));
		
//		Set exit values:
		
		this.exitValues.add("exit");
		this.exitValues.add("quit");
		
//		Greeting:
		
		System.out.println("Hello, and welcom to sysboard shogi CLI edition. To exit at any time, just enter the word exit. To load a game, enter the word load. To save a game, enter the word save.");
		
		this.load();
	}
	
	private String getRepresentation(Piece piece) {
		String[] parts = piece.getRepresentation().split("\\.");
		for (Set set : this.sets) {
			if (set.getName().equals(parts[0])){
				return set.getRepresentationByPiece(parts[1]);
			}
		}
		
		return this.chars.get(this.driver.getTypes().indexOf(piece.getUpgradeType())) + "";
	}
	
	private char getCharRepresentation(String type) {
		return type.charAt(0);
	}
	
	private boolean alreadyExists(char c) {
		c = Character.toLowerCase(c);
		for (Character ch : chars) {
			ch = Character.toLowerCase(ch);
			if (ch.equals(c)) {
				return true;
			}
		}
		
		return false;
	}
	
	public static void main(String[] args) {
		new CLI();
	}
	
	private void invalid(String message) {
		System.out.println(message);
	}
}
